#include <iostream>
#include <string>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>


/**
   This is the action to be taken.
*/
enum pa_t 
{ 
    pa_encrypt = 0, 
    pa_decrypt = 1, 
    pa_default = -1 
};

/**
   This displays information about the usage of the executable.
   @param n program name
*/
void 
usage(const char * n)
{
    std::string name(n);

    std::cout << name << ": usage " 
		<< std::endl <<
        name << " -k <key> -i <infile> -o <outfile> -e*|d "
		<< std::endl;

    exit(1);
}

void 
error(const char * n, const char * m)
{
    std::string name(n);

    std::cerr << name << (m ? m : "") << (m ? ": " : "") << strerror(errno) << std::endl;
    exit(2);
}

/**
   This executes the program.
   @param argc argument count
   @param argv argument vector
*/



/**
   This is the base class of the agents employed.
*/

struct crypt_agent 
{
    virtual int operator()(const std::string &, int, int) = 0;
};


/**

   This is the encryption agent.

   This encrypts the input stream and places the results on the 
   output stream.
   @param k key
   @param id input file
   @param od output file
   @return 0 on success, error code otherwise

*/
struct encryption_agent : public crypt_agent
{ 
    int operator()(const std::string & k, int id, int od)
	{
		unsigned int len = 0, pos = 0;
		unsigned char ibuf[1024] = { 0 }, obuf[sizeof(ibuf)] = { 0 };

    	while(int(len = read(id, ibuf, sizeof(ibuf))) > 0)
    	{
        	unsigned int j = 0;
        	for (j = 0; j < len; j++, pos %= k.length())
			{
	        	 obuf[j] = ~ibuf[j] + k[pos++];
			}
			
        	if (write(od, obuf, len) != int(len))			
	        	return -1;
    	}

    	return 0;
	}	
};


/**
   This is the decryption agent.

   This decrypts the input stream and places the results on the output 
   stream.
   @param k key
   @param id input file
   @param od output file
   @return 0 on success, error code otherwise
   
*/
struct decryption_agent : public crypt_agent
{
    int operator()(const std::string & k, int id, int od)
	{
		unsigned int len = 0, pos = 0;
		unsigned char ibuf[1024] = { 0 }, obuf[sizeof(ibuf)] = { 0 };

    	while(int(len = read(id, ibuf, sizeof(ibuf))) > 0)
    	{
        	unsigned int j = 0;
        	for (j = 0; j < len; j++, pos %= k.length())
			{
	        	obuf[j] = ~(ibuf[j] - k[pos++]);
			}
			
        	if (write(od, obuf, len) != int(len))
	        	return -1;
    	}

    	return 0;
	}

};




int 
main(int argc, char **argv)
{
using std::string;

    pa_t pa = pa_default;
    string key, inf, outf;
    int i, ifd, ofd;
  
    encryption_agent ea;
    decryption_agent da;
    crypt_agent *a[2] = { &ea, &da };

    for(i = 1; i < argc; )
    {
        if(*argv[i] != '-')
            usage(*argv);
        else
        {
            switch(*(argv[i] + 1))
	        {
                case 'k': 
	                if (++i == argc) 
                        usage(*argv);
	                else 
                        key = argv[i++];
	            break;
                
            	case 'i':
	                if (++i == argc) 
                        usage(*argv);
	                else 
                        inf = argv[i++];
	            break;
	            
                case 'o':
	                if (++i == argc) 
                        usage(*argv);
	                else 
                        outf = argv[i++];
	            break;

            	case 'd':
	                if (pa != pa_default) 
                        usage(*argv);
	                else 
                        pa = pa_decrypt;
	                i++;
	            break;
	            
                case 'e':
	                if (pa != pa_default) 
                        usage(*argv);
	                else 
                        pa = pa_encrypt;
	                i++;
	            break;

            	default:
	                usage(*argv);
            }
        }
    }  

    if(pa == pa_default) 
        pa = pa_encrypt;

    if(key.empty()) 
        usage(*argv);
    else 
    if ((ifd = inf.empty() ? STDIN_FILENO : open(inf.c_str(), O_RDONLY)) < 0)
        error(*argv, "input file open failure");
    else
    if ((ofd = outf.empty() ? STDOUT_FILENO :
	   open(outf.c_str(), O_WRONLY | O_TRUNC | O_CREAT, 0600)) < 0)
        error(*argv, "output file open failure");

    if ((*a[pa])(key, ifd, ofd))
        error(*argv, "write error: ");
    else
        return 0;
}
